The Common Lisp structure
mechanism provides a general way to define data types similar to
C's struct types. A structure is a Lisp object
containing some number of slots, each of which can
hold any Lisp data object. Functions are provided for accessing
and setting the slots, creating or copying structure objects, and
recognizing objects of a particular structure type.
In true Common Lisp, each structure type is a new type distinct from all existing Lisp types. Since the underlying Emacs Lisp system provides no way to create new distinct types, this package implements structures as vectors (or lists upon request) with a special “tag” symbol to identify them.
The
defstructform defines a new structure type called name, with the specified slots. (The slots may begin with a string which documents the structure type.) In the simplest case, name and each of the slots are symbols. For example,(defstruct person name age sex)defines a struct type called
personwhich contains three slots. Given apersonobject p, you can access those slots by calling(person-namep),(person-agep), and(person-sexp). You can also change these slots by usingsetfon any of these place forms:(incf (person-age birthday-boy))You can create a new
personby callingmake-person, which takes keyword arguments:name,:age, and:sexto specify the initial values of these slots in the new object. (Omitting any of these arguments leaves the corresponding slot “undefined,” according to the Common Lisp standard; in Emacs Lisp, such uninitialized slots are filled withnil.)Given a
person,(copy-personp)makes a new object of the same type whose slots areeqto those of p.Given any Lisp object x,
(person-px)returns true if x looks like aperson, false otherwise. (Again, in Common Lisp this predicate would be exact; in Emacs Lisp the best it can do is verify that x is a vector of the correct length which starts with the correct tag symbol.)Accessors like
person-namenormally check their arguments (effectively usingperson-p) and signal an error if the argument is the wrong type. This check is affected by(optimize (safety ...))declarations. Safety level 1, the default, uses a somewhat optimized check that will detect all incorrect arguments, but may use an uninformative error message (e.g., “expected a vector” instead of “expected aperson”). Safety level 0 omits all checks except as provided by the underlyingarefcall; safety levels 2 and 3 do rigorous checking that will always print a descriptive error message for incorrect inputs. See Declarations.(setq dave (make-person :name "Dave" :sex 'male)) ⇒ [cl-struct-person "Dave" nil male] (setq other (copy-person dave)) ⇒ [cl-struct-person "Dave" nil male] (eq dave other) ⇒ nil (eq (person-name dave) (person-name other)) ⇒ t (person-p dave) ⇒ t (person-p [1 2 3 4]) ⇒ nil (person-p "Bogus") ⇒ nil (person-p '[cl-struct-person counterfeit person object]) ⇒ tIn general, name is either a name symbol or a list of a name symbol followed by any number of struct options; each slot is either a slot symbol or a list of the form ‘(slot-name default-value slot-options...)’. The default-value is a Lisp form which is evaluated any time an instance of the structure type is created without specifying that slot's value.
Common Lisp defines several slot options, but the only one implemented in this package is
:read-only. A non-nilvalue for this option means the slot should not besetf-able; the slot's value is determined when the object is created and does not change afterward.(defstruct person (name nil :read-only t) age (sex 'unknown))Any slot options other than
:read-onlyare ignored.For obscure historical reasons, structure options take a different form than slot options. A structure option is either a keyword symbol, or a list beginning with a keyword symbol possibly followed by arguments. (By contrast, slot options are key-value pairs not enclosed in lists.)
(defstruct (person (:constructor create-person) (:type list) :named) name age sex)The following structure options are recognized.
:conc-name- The argument is a symbol whose print name is used as the prefix for the names of slot accessor functions. The default is the name of the struct type followed by a hyphen. The option
(:conc-name p-)would change this prefix top-. Specifyingnilas an argument means no prefix, so that the slot names themselves are used to name the accessor functions.:constructor- In the simple case, this option takes one argument which is an alternate name to use for the constructor function. The default is
make-name, e.g.,make-person. The above example changes this tocreate-person. Specifyingnilas an argument means that no standard constructor should be generated at all.In the full form of this option, the constructor name is followed by an arbitrary argument list. See Program Structure, for a description of the format of Common Lisp argument lists. All options, such as
&restand&key, are supported. The argument names should match the slot names; each slot is initialized from the corresponding argument. Slots whose names do not appear in the argument list are initialized based on the default-value in their slot descriptor. Also,&optionaland&keyarguments which don't specify defaults take their defaults from the slot descriptor. It is valid to include arguments which don't correspond to slot names; these are useful if they are referred to in the defaults for optional, keyword, or&auxarguments which do correspond to slots.You can specify any number of full-format
:constructoroptions on a structure. The default constructor is still generated as well unless you disable it with a simple-format:constructoroption.(defstruct (person (:constructor nil) ; no default constructor (:constructor new-person (name sex &optional (age 0))) (:constructor new-hound (&key (name "Rover") (dog-years 0) &aux (age (* 7 dog-years)) (sex 'canine)))) name age sex)The first constructor here takes its arguments positionally rather than by keyword. (In official Common Lisp terminology, constructors that work By Order of Arguments instead of by keyword are called “BOA constructors.” No, I'm not making this up.) For example,
(new-person "Jane" 'female)generates a person whose slots are"Jane", 0, andfemale, respectively.The second constructor takes two keyword arguments,
:name, which initializes thenameslot and defaults to"Rover", and:dog-years, which does not itself correspond to a slot but which is used to initialize theageslot. Thesexslot is forced to the symbolcaninewith no syntax for overriding it.:copier- The argument is an alternate name for the copier function for this type. The default is
copy-name.nilmeans not to generate a copier function. (In this implementation, all copier functions are simply synonyms forcopy-sequence.):predicate- The argument is an alternate name for the predicate which recognizes objects of this type. The default is name
-p.nilmeans not to generate a predicate function. (If the:typeoption is used without the:namedoption, no predicate is ever generated.)In true Common Lisp,
typepis always able to recognize a structure object even if:predicatewas used. In this package,typepsimply looks for a function called typename-p, so it will work for structure types only if they used the default predicate name.:include- This option implements a very limited form of C++-style inheritance. The argument is the name of another structure type previously created with
defstruct. The effect is to cause the new structure type to inherit all of the included structure's slots (plus, of course, any new slots described by this struct's slot descriptors). The new structure is considered a “specialization” of the included one. In fact, the predicate and slot accessors for the included type will also accept objects of the new type.If there are extra arguments to the
:includeoption after the included-structure name, these options are treated as replacement slot descriptors for slots in the included structure, possibly with modified default values. Borrowing an example from Steele:(defstruct person name (age 0) sex) ⇒ person (defstruct (astronaut (:include person (age 45))) helmet-size (favorite-beverage 'tang)) ⇒ astronaut (setq joe (make-person :name "Joe")) ⇒ [cl-struct-person "Joe" 0 nil] (setq buzz (make-astronaut :name "Buzz")) ⇒ [cl-struct-astronaut "Buzz" 45 nil nil tang] (list (person-p joe) (person-p buzz)) ⇒ (t t) (list (astronaut-p joe) (astronaut-p buzz)) ⇒ (nil t) (person-name buzz) ⇒ "Buzz" (astronaut-name joe) ⇒ error: "astronaut-name accessing a non-astronaut"Thus, if
astronautis a specialization ofperson, then everyastronautis also aperson(but not the other way around). Everyastronautincludes all the slots of aperson, plus extra slots that are specific to astronauts. Operations that work on people (likeperson-name) work on astronauts just like other people.:print-function- In full Common Lisp, this option allows you to specify a function which is called to print an instance of the structure type. The Emacs Lisp system offers no hooks into the Lisp printer which would allow for such a feature, so this package simply ignores
:print-function.:type- The argument should be one of the symbols
vectororlist. This tells which underlying Lisp data type should be used to implement the new structure type. Vectors are used by default, but(:type list)will cause structure objects to be stored as lists instead.The vector representation for structure objects has the advantage that all structure slots can be accessed quickly, although creating vectors is a bit slower in Emacs Lisp. Lists are easier to create, but take a relatively long time accessing the later slots.
:named- This option, which takes no arguments, causes a characteristic “tag” symbol to be stored at the front of the structure object. Using
:typewithout also using:namedwill result in a structure type stored as plain vectors or lists with no identifying features.The default, if you don't specify
:typeexplicitly, is to use named vectors. Therefore,:namedis only useful in conjunction with:type.(defstruct (person1) name age sex) (defstruct (person2 (:type list) :named) name age sex) (defstruct (person3 (:type list)) name age sex) (setq p1 (make-person1)) ⇒ [cl-struct-person1 nil nil nil] (setq p2 (make-person2)) ⇒ (person2 nil nil nil) (setq p3 (make-person3)) ⇒ (nil nil nil) (person1-p p1) ⇒ t (person2-p p2) ⇒ t (person3-p p3) ⇒ error: function person3-p undefinedSince unnamed structures don't have tags,
defstructis not able to make a useful predicate for recognizing them. Also, accessors likeperson3-namewill be generated but they will not be able to do any type checking. Theperson3-namefunction, for example, will simply be a synonym forcarin this case. By contrast,person2-nameis able to verify that its argument is indeed aperson2object before proceeding.:initial-offset- The argument must be a nonnegative integer. It specifies a number of slots to be left “empty” at the front of the structure. If the structure is named, the tag appears at the specified position in the list or vector; otherwise, the first slot appears at that position. Earlier positions are filled with
nilby the constructors and ignored otherwise. If the type:includes another type, then:initial-offsetspecifies a number of slots to be skipped between the last slot of the included type and the first new slot.
Except as noted, the defstruct facility of this
package is entirely compatible with that of Common Lisp.